/*******************************************************************************
 * Copyright (c) 2002 Boris Pruessmann and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Common Public License v0.5 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.ui.internal.model;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.api.IChangesResult;
import net.sourceforge.perforce.core.api.IOpenedResult;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IResolveResult;
import net.sourceforge.perforce.core.api.IResolvedResult;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.resources.IP4File;
import net.sourceforge.perforce.ui.PerforceUIPlugin;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * This class performs caching for the PendingChangelistView.
 * 
 * @version $Revision: 488 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 */
public class PendingChangelistCache {
  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores the Perforce server instance. */
  private final IPerforceServer server;
  /** Stores the changelist cache. */
  private final Map<String, ChangelistElement> changelistCache = Maps.newHashMap();
  /** Stores the file cache. */
  private final Map<String, IP4File> fileCache = Maps.newHashMap();
  /** Indicates whether the cache is valid or not. */
  private boolean isValid = false;
  /** Indicates whether when have to collect all opened info. */
  private boolean performOpenedAll = false;

  // -------------------------------------------------------------------------
  // Public Constructors

  /**
   * Constructor for PendingChangelistCache.
   * 
   * @param theServer the Perforce server.
   */
  public PendingChangelistCache(IPerforceServer theServer) {
    server = theServer;

    changelistCache.put(createKey(IChangelist.DEFAULT), new DefaultChangelistElement(theServer,
        this));
  }

  //----------------------------------------------------------------------------
  // -- Public Methods

  /**
   * Sets the 'performOpenedAll' value.
   * 
   * @param newPerformOpenedAll the new value.
   */
  public void setPerformOpenedAll(boolean newPerformOpenedAll) {
    performOpenedAll = newPerformOpenedAll;
  }

  /**
   * Called to retrieve the changelist elements.
   * 
   * @param comparator used for selecting items from the cache.
   * @return array of changelist elements.
   */
  public synchronized IChangelist[] getChangelistElements(Comparator<IChangelist> comparator) {
    refreshCache();

    List<IChangelist> resultList = Lists.newArrayList();
    for (IChangelist o : changelistCache.values()) {
      if (comparator.equals(o)) {
        resultList.add(o);
      }
    }

    IChangelist[] result = resultList.toArray(new ChangelistElement[resultList.size()]);
    Arrays.sort(result, comparator);

    return result;
  }

  /**
   * Called to retrieve the file elements.
   * 
   * @param comparator used for selecting items from the cache.
   * @return array of file elements.
   */
  public synchronized IP4File[] getFileElements(Comparator<?> comparator) {
    refreshCache();

    List<IP4File> result = Lists.newArrayList();
    for (IP4File file : fileCache.values()) {
      if (comparator.equals(file)) {
        result.add(file);
      }
    }

    return result.toArray(new IP4File[result.size()]);
  }

  /** Called to invalidate the cache. */
  public synchronized void invalidate() {
    isValid = false;
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  /** Called to refresh part of the cache. */
  private void refreshCache() {
    if (isValid) {
      return;
    }

    try {
      HashSet<String> fileSet = new HashSet<String>();

      HashSet<String> changelistSet = new HashSet<String>();
      changelistSet.add(createKey(IChangelist.DEFAULT));

      // Gather infos
      IOpenedResult[] openedResults = server.opened(performOpenedAll);
      IResolveResult[] resolveResults =
          server.resolve(null, null, IPerforceServer.RESOLVE_SIMULATE);
      IResolvedResult[] resolvedResults = server.resolved(null);
      IChangesResult[] changesResults =
          server.changes(IChangelist.STATUS_PENDING, Integer.MAX_VALUE);

      // Process changesResults
      for (int i = 0; i < changesResults.length; i++) {
        IChangesResult changesResult = changesResults[i];
        String key = createKey(changesResult.getChangelist(), changesResult.getClientName());
        changelistSet.add(key);


        ChangelistElement changelist = changelistCache.get(key);
        if (changelist == null) {
          changelist = new ChangelistElement(server, this, changesResult.getChangelist());
          changelistCache.put(key, changelist);
        }

        changelist.setUserName(changesResult.getUserName());
        changelist.setClientName(changesResult.getClientName());
        changelist.setDate(changesResult.getDate());
        changelist.setDescription(changesResult.getDescription());
      }

      // process openedResults
      for (int i = 0; i < openedResults.length; i++) {
        IOpenedResult openedResult = openedResults[i];
        String key = createKey(openedResult.getDepotFilename(), openedResult.getClientName());
        fileSet.add(key);

        P4File file = (P4File) fileCache.get(key);
        if (file == null) {
          file = new P4File(server);
          fileCache.put(key, file);
        }

        if (openedResult.getChangelist().equals(IChangelist.DEFAULT)
            && !openedResult.getClientName().equals(server.getClient())) {
          ChangelistElement changelist = new ChangelistElement(server, this, IChangelist.DEFAULT);

          changelist.setUserName(openedResult.getUserName());
          changelist.setClientName(openedResult.getClientName());
          changelist.setDate(new Date());
          changelist.setDescription(""); //$NON-NLS-1$

          key = createKey(changelist.getId(), changelist.getClientName());
          changelistCache.put(key, changelist);
          changelistSet.add(key);
        }

        file.setChangelistId(openedResult.getChangelist());
        file.setAction(openedResult.getAction());
        file.setDepotFilename(openedResult.getDepotFilename());
        file.setClientName(openedResult.getClientName());
        file.setRevision(openedResult.getRevision());
        file.setOurLock(openedResult.hasOurLock());
        file.setFileType(openedResult.getFileType());
        file.setNeedsResolve(false);
      }

      // process resolveResults
      for (int i = 0; i < resolveResults.length; i++) {
        IResolveResult resolveResult = resolveResults[i];
        String key = createKey(resolveResult.getDepotFilename(), server.getClient());

        P4File file = (P4File) fileCache.get(key);
        if (file != null) {
          file.setNeedsResolve(true);
        }
      }

      // process resolvedResults
      for (int i = 0; i < resolvedResults.length; i++) {
        IResolvedResult resolvedResult = resolvedResults[i];
        String key = createKey(resolvedResult.getDepotFilename(), server.getClient());

        P4File file = (P4File) fileCache.get(key);
        if (file != null) {
          file.setResolved(true);
        }
      }

      // Removed changelists that don't exist anymore
      for (Iterator<Map.Entry<String, ChangelistElement>> it =
          changelistCache.entrySet().iterator(); it.hasNext();) {
        Map.Entry<String, ChangelistElement> entry = it.next();
        if (!changelistSet.contains(entry.getKey())) {
          it.remove();
        }
      }

      // Remove files that don't exist anymore
      for (Iterator<Map.Entry<String, IP4File>> it = fileCache.entrySet().iterator(); it.hasNext();) {
        Map.Entry<String, IP4File> entry = it.next();
        if (!fileSet.contains(entry.getKey())) {
          it.remove();
        }
      }

    } catch (PerforceException e) {
      PerforceUIPlugin.log(e.getStatus());
    } finally {
      isValid = true;
    }
  }

  /**
   * Creates a key for the changelist cahce.
   * 
   * @param id the changelist id.
   * @return the generated key.
   */
  private String createKey(Integer id) {
    return createKey(id, server.getClient());
  }

  /**
   * Creates a key for the changelist cahce.
   * 
   * @param id the changelist id.
   * @param clientName the client name
   * @return the generated key.
   */
  private String createKey(Integer id, String clientName) {
    return createKey(id.toString(), clientName);
  }

  /**
   * Creates a key for the changelist cahce.
   * 
   * @param text some misc. text.
   * @param clientName the client name
   * @return the generated key.
   */
  private String createKey(String text, String clientName) {
    return text + '@' + clientName;
  }
}
